(function(window) {
    /* 初始配置（外部使用）
    */
    let $init = {
        selector:"",    // 必填，容器ID
        data:[],        // 必填，初始节点集
        width:null,      // 容器宽
        height:null,     // 容器高
        canvasScale:10, // 无限画布比例 例canvasScale=2 2倍于容器大小
        scale:{         // 鼠标缩放比例
            max:1.6,
            min:0.6,
        },
        initFn:()=>{},  // 节点更新后需要重新初始化的操作（自定义）
        editable:true,  // 节点是否可编辑
        style:{         // 自定义样式
            line:{
                colors:['#3BA9F6'],
                size:3,
            },
            contactLine:{
                colors:['#F6753B'],
                size:3,
            },
            node:{
                class:'',           // 所有节点的样式
                cls:[],             // 多节点样式
                space_x:110,        // 节点之间水平间距
                space_y:28,         // 节点之间垂直间距
                pull:{}
            },
            contextMenu:{
                class:''
            },
            noSelectionArea:{       // 禁选区域样式
                bgColor:"rgba(255,255,255,.1)",
                borderWidth:100 ,   // 左右额外区域
            },
        },
        contextMenu:{   // 自定义右键
            addItem:[],
        }
    }
    let $s      = null; // 最外层容器元素
    let $e      = null; // 画布容器
    let $nEls   = null; // 节点元素集
    let $scale  = 1; // 缩放比例
    let $w_s    = 0, // 最外层容器-宽
        $h_s    = 0, // 最外层容器-高
        $w_e    = 0, // 画布容器-宽
        $h_e    = 0; // 画布容器-高
    let $begin_xy = []; // 初始XY值
    
    // 节点的事件操作
    let $nodeEvt = {
        draw:false,
        cntMenu:false,
        contactLine:false,
    };

    // 本地数据库-索引为id
    let __DB__  = {
        nodes:{},// 普通节点
        frees:{},// 自由节点
        lines:{},// 线
        contactLines:{},// 关联线
    };

    // 本地数据集-索引是下标
    let __Arr__ = {
        nodes:[],// 普通节点
        frees:[],// 自由节点
        lines:[],// 线
        contactLines:[],// 关联线
    }

    // 普通节点的多维树型结构
    let $nodesMany = [];

    // 初始化xmind
    window.$xmind = class {
        constructor(res){
            if(!res)return;
            
            let selector = res.selector;
            let data     = res.data;

            // 创建容器
            $s = document.getElementById(selector);
            let main = document.createElement('div');
            main.setAttribute('id','canvas');
            main.setAttribute('class','xmd-dCanvas');
            $s.appendChild(main);

            // 创建画布容器
            $e = document.getElementById('canvas');
            $e.oncontextmenu = (e)=>{
                e.stopPropagation();
                e.preventDefault();
            }
            
            $init = __tool__.extends($init,res);
            __cntMenu__.addItemfn();// 初始化右键

            this.style();//容器的尺寸

            if(data.length > 0){
                __canvas__.load({items:data});// 初始画布数据
            }
        }

        //容器的尺寸
        style(){
            let w = $init.width || parseFloat($s.getBoundingClientRect().width),
                h = $init.height || parseFloat($s.getBoundingClientRect().height);
            $s.style.width  = w + "px";
            $s.style.height = h + "px";

            let m = $init.canvasScale;//无限画布比例 例m=2 2倍于容器大小
            $w_s = w;
            $h_s = h;
            $w_e = w*m;
            $h_e = h*m;

            $begin_xy = [(w*m-w)/2,(h*m-h)/2];

            $e.style.cssText = `
                top:-${(h*m-h)/2}px;
                left:-${(w*m-w)/2}px;
                width:${w*m}px;
                height:${h*m}px;
            `;
        }

        /*
            新增节点
            xmid.addNode({
                pid     : "",// 必填
                label   : "",// 必填
                left    : false,  // 必填
                html    : ``,
                fn      : ()=>{}
            });
        */
        addNode(res){
            let obj = {
                pid     : res.pid,
                id      : res.id||"new_"+(new Date).getTime(),
                root    : false,
                label   : res.label||"",
                left    : res.left||false,  
                type    : "text", //暂未开发，请使用HTML自定义内容
                html    : res.html||null,
            };

            let rb = {
                msg:"success",
                data:null,
            }
            
            if(__DB__.nodes[obj.id]){
                rb.msg = "fail";
                console.log("新增节点失败");
                if(typeof res.fn)res.fn(rb);
                return;
            }            
            
            __Arr__.nodes.push(obj);
            __DB__.nodes[obj.id] = __tool__.deepCopy(obj);
            __canvas__.load();

            rb.data = __DB__.nodes[obj.id];
            if(typeof res.fn)res.fn(rb);
        }

        /*
            删除节点
            xmid.delNode({
                id      : "", // 必填
                fn      : ()=>{}
            });
        */
        delNode(res){
            let id = res.id;
            let rb = {
                msg:"success"
            }

            if(!!id){
                if(!__DB__.nodes[id]){
                    rb.msg = "fail";
                    console.log("删除节点失败");
                    if(res.fn && typeof res.fn)res.fn(rb);
                    return;
                }

                let the = __DB__.nodes[id];
                __Arr__.nodes.splice(the.index,1);
                delete the[id];
                
                __Arr__.nodes.forEach((el,i)=>{
                    el.index = i;
                    __DB__.nodes[el.id].index = i;
                })

                __canvas__.load();

                if(res.fn && typeof res.fn)res.fn(rb);
            }
            else{
                rb.msg = "fail";
                if(typeof res.fn)res.fn(rb);
            }
        }

        // 重置节点集
        setNodes(items){
            if(!items)return;
            if(!(items instanceof Array))return;
            if(items.length === 0)return;

            __Arr__.nodes = __tool__.deepCopy(items);
            __canvas__.load();
        }

        // 刷新
        refresh(){
            __canvas__.load();
        }

        // 获取树节点集
        getNodesTree(){
            if(__Arr__.nodes.length === 0)return [];
            let arr = __Arr__.nodes.map(item=>{
                return this.getRebackData(item);
            });
            return arr;
        }

        // 获取自由节点集
        getNodesFree(){
            if(__Arr__.frees.length === 0)return [];
            let arr = __Arr__.frees.map(item=>{
                return this.getRebackData(item);
            });
            return arr;
        }

        // 获取单个节点 getNode({id:""});
        getNode(res){
            let id = res.id||null;
            if(!id)return;

            let index = __DB__.nodes[id].index;
            return this.getRebackData(__Arr__.nodes[index]);
        }

        // 过滤返回值
        getRebackData(item){
            let obj = {
                id          : item.id,
                pid         : item.pid,
                root        : item.root,
                label       : item.label,
                type        : item.type,
                nodeType    : item.nodeType,
                left        : item.left,
                index       : item.index,
                // indexMany   : item.indexMany,
            }
            return obj;
        }

        // 居中
        setCenter(){
            $e.style.left = -$begin_xy[0] + "px";
            $e.style.top  = -$begin_xy[1] + "px";
        }

        // 事件
        on(name,fn){
            if(name === "click"){
                this.onClick(fn);
                return
            }

        }

        onClick(fn){
            
            window.addEventListener('nodeClick', function(event){
                fn(event);
            });

            
        }
    }

    // 画布模块
    class xCanvas {
        constructor(){
            this.x = 0;
            this.y = 0;
        }
        // 初始化画面内容
        load(res){
            let items = res&&res.items||__Arr__.nodes;
            let type = res&&res.type||"init";

            if(type === 'init'){
                this.clear();// 清除画布内容
                __format__.clear();// 清除画布的内容
                for(let i in items){
                    __format__.nodeInit(items[i],i);
                }
            }

            this.reset();// 重置节点数据
            this.auto();// 开始无限画布
            __node__.evt();
            __line__.show();
            __opt__.pull();
            $init.initFn();
        }

        // 解析生成节点数据
        reset(){
            $nEls = document.querySelectorAll('.xmd-node');
            for(let el of $nEls){
                let n_w = el.getBoundingClientRect().width,
                    n_h = el.getBoundingClientRect().height,
                    id  = el.getAttribute('id');

                    n_w = n_w/$scale;
                    n_h = n_h/$scale;
                __DB__.nodes[id].init.size = {
                    w:n_w,
                    h:n_h,
                }
                let i = __DB__.nodes[id].index;
                __Arr__.nodes[i].init.size = {
                    w:n_w,
                    h:n_h,
                }
                if(el.getAttribute('id') === "root"){
                    let x = $w_e/2 - n_w/2,
                        y = $h_e/2 - n_h/2;
                    el.style.left = `${x}px`;
                    el.style.top  = `${y}px`;

                    __DB__.nodes[id].init.poi = {x,y};
                    __Arr__.nodes[i].init.poi = {x,y};

                    let itp = __tool__.idTrunPoi(id);//获取节点坐标信息
                    __DB__.nodes[id].init.point = itp.point;
                    __Arr__.nodes[i].init.point = itp.point;
                }
            }
            
            __format__.node();//渲染节点
        }

        // 节点模板
        node(res){
            // 自定义节点样式
            let divClass = $init.style.node.class?" "+$init.style.node.class:"";
            if($init.style.node.cls.length > 0){
                $init.style.node.cls.forEach(el=>{
                    if(el.id){
                        divClass = res.id === el.id?divClass + " " + el.name:divClass;
                    }
                    else if(res.clsType === el.type){
                        divClass = divClass + " " + el.name;
                    }
                });
            }

            if(res.root)divClass = divClass + " xmd-node-root"; 

            let _node = document.createElement('div');
            _node.setAttribute('id',res.id);
            _node.setAttribute('pid',res.pid);
            _node.setAttribute('class','xmd-node'+divClass);

            let _nodeLabel = document.createElement('div');
            _nodeLabel.setAttribute('class','node-txt');
            // _nodeLabel.setAttribute('contenteditable','true');
            _nodeLabel.innerHTML = res.label;
            
            let _nodeHtml = document.createElement('div');
            _nodeHtml.setAttribute('class','node-html');
            if(res.html && typeof res.html === 'string')_nodeHtml.innerHTML = res.html;

            let _nodeUp = document.createElement('div');
            _nodeUp.setAttribute('class','xmd-node-up');

            let _nodeDown = document.createElement('div');
            _nodeDown.setAttribute('class','xmd-node-down');

            let _nodeIn = document.createElement('div');
            _nodeIn.setAttribute('class','xmd-node-in');

            _node.appendChild(_nodeLabel);
            _node.appendChild(_nodeHtml);
            _node.appendChild(_nodeUp);
            _node.appendChild(_nodeDown);
            _node.appendChild(_nodeIn);
            
            let id = _node.getAttribute('id');
            if(__DB__.nodes[id].root){
                let _nodePullLeft  = document.createElement('div');
                let _nodePullRight = document.createElement('div');
                _nodePullLeft.setAttribute('class','xmd-node-pull-l');
                _nodePullRight.setAttribute('class','xmd-node-pull-r');
                _node.appendChild(_nodePullLeft);
                _node.appendChild(_nodePullRight);
            }
            else{
                if(__DB__.nodes[id].left){
                    let _nodePullLeft = document.createElement('div');
                    _nodePullLeft.setAttribute('class','xmd-node-pull-l');
                    _node.appendChild(_nodePullLeft);
                }
                else{
                    let _nodePullRight = document.createElement('div');
                    _nodePullRight.setAttribute('class','xmd-node-pull-r');
                    _node.appendChild(_nodePullRight);
                }
            }
            
            
            return _node;
        }

        // 开始无限画布
        auto(){
            $e.onmousedown = (e)=>{
                //获取鼠标按下的坐标
				let x1 = e.clientX,
                    y1 = e.clientY;
                
                let nowX = e.target.offsetLeft,
                    nowY = e.target.offsetTop;
  
                //给可视区域添加鼠标的移动事件
				document.onmousemove = (ev)=>{
                    document.body.style.cursor='move';
					//获取鼠标移动时的坐标
					let x2 = ev.clientX;
					let y2 = ev.clientY;

					//计算出鼠标的移动距离
					let x = x2 - x1;
					let y = y2 - y1;

					this.x = x;
					this.y = y;

                    $e.style.left = (nowX + x) + "px";
                    $e.style.top  = (nowY + y) + "px";
				};

				document.onmouseup = (ev)=>{
					document.onmousemove = null;
					document.onmouseup = null;
                    document.body.style.cursor='default';
				};
            }

            $e.onmousewheel = (e)=>{
                let max = $init.scale.max,
                    min = $init.scale.min;
				if(e.wheelDelta > 0){
					if($scale > max)return;
					$scale = parseFloat($scale) + 0.1;
					$scale = $scale.toFixed(1);
				}
				else{
					if($scale < min)return;
					$scale = parseFloat($scale) - 0.1;
					$scale = $scale.toFixed(1);
				}

                $e.style.transform = `scale(${$scale})`;
			}	
        }

        // 清除画布内容
        clear(){
            $e.innerHTML = "";
        }
    }

    // 操作模块
    class xOpt {
        constructor(){}
        pull(){
            let el_ls = document.querySelectorAll('.xmd-node-pull-l');
            let el_rs = document.querySelectorAll('.xmd-node-pull-r');
            if(el_rs.length > 0){
                el_rs.forEach(el=>{
                    el.onclick = (e)=>{
                        e.stopPropagation();
                        e.preventDefault();
                        ck.call(this,{el,e});
                    }
                })
            }

            if(el_ls.length > 0){
                el_ls.forEach(el=>{
                    el.onclick = (e)=>{
                        e.stopPropagation();
                        e.preventDefault();
                        ck.call(this,{el,e});
                    }
                })
            }

            function ck(res) {
                let el = res.el;
                let e = res.e;
                let id = el.parentNode.getAttribute('id');

                let is = el.classList;
                let newCls = is[1] === "xmd-node-pull-remove"?"xmd-node-pull-plus":"xmd-node-pull-remove";
                el.setAttribute('class',is[0] + " " + newCls);

                let plus = null;
                let the = __DB__.nodes[id];
                if(the.root){
                    if(is[0] === "xmd-node-pull-l"){
                        the.init.pull.l.plus = !the.init.pull.l.plus;
                        // the.init.pull.l.open = !the.init.pull.l.open;
                        __Arr__.nodes[the.index].init.pull.l.plus = the.init.pull.l.plus;
                        // __Arr__.nodes[the.index].init.pull.l.open = the.init.pull.l.open;
                    }
                    else if(is[0] === "xmd-node-pull-r"){
                        the.init.pull.r.plus = !the.init.pull.r.plus;
                        // the.init.pull.r.open = !the.init.pull.r.open;
                        __Arr__.nodes[the.index].init.pull.r.plus = the.init.pull.r.plus;
                        // __Arr__.nodes[the.index].init.pull.r.open = the.init.pull.r.open;
                    }
                }
                else{
                    if(the.left){
                        the.init.pull.l.plus = !the.init.pull.l.plus;
                        // the.init.pull.l.open = !the.init.pull.l.open;
                        __Arr__.nodes[the.index].init.pull.l.plus = the.init.pull.l.plus;
                        // __Arr__.nodes[the.index].init.pull.l.open = the.init.pull.l.open;
                    }
                    else{
                        the.init.pull.r.plus = !the.init.pull.r.plus;
                        the.init.pull.r.open = !the.init.pull.r.open;
                        __Arr__.nodes[the.index].init.pull.r.plus = the.init.pull.r.plus;
                        // __Arr__.nodes[the.index].init.pull.r.open = the.init.pull.r.open;
                    }
                }
                
                __canvas__.load({item:__Arr__.nodes,type:"noinit"});
            }
        }
    }

    // 线模块
    class xLine {
        constructor(){
            this.colors = $init.style.contactLine.colors;
        }
        add(res,tt){
            let elSVG = document.getElementById('svg');
            let x1 = res.x1,
                x2 = res.x2,
                y1 = res.y1,
                y2 = res.y2;
            
            let id = res.id;

            let color = this.colors[0];
            let contactLineSize = $init.style.contactLine.size;
            let line = `
                <line 
                    id="${id}" 
                    x1="${x1}" 
                    y1="${y1}" 
                    x2="${x2}" 
                    y2="${y2}" 
                    style="stroke:${color};stroke-width:${contactLineSize};z-index:10;pointer-events:visiblePainted" 
                    />
            `;
            
            elSVG.innerHTML += line;
        }
        add_contact(res){
            let elSVG = document.getElementById('svg_contact');
            let x1 = res.x1,
                x2 = res.x2,
                y1 = res.y1,
                y2 = res.y2;
            
            let id = res.id;

            let color = this.colors[0];
            let contactLineSize = $init.style.contactLine.size;
            let line = `
                <line 
                    id="${id}" 
                    x1="${x1}" 
                    y1="${y1}" 
                    x2="${x2}" 
                    y2="${y2}" 
                    style="stroke:${color};stroke-width:${contactLineSize};z-index:10;pointer-events:visiblePainted" 
                    />
            `;
            
            elSVG.innerHTML += line;
        }
        del(res){
            if(!res || Object.keys(res).length === 0)return;
            let id = res.id;
            if(!id)return;
            let elSVG = document.getElementById('svg');
            let elLine = document.getElementById(id);
            if(elLine)elSVG.removeChild(elLine);
        }
        del_contact(res){
            if(!res || Object.keys(res).length === 0)return;
            let id = res.id;
            if(!id)return;
            let elSVG = document.getElementById('svg_contact');
            let elLine = document.getElementById(id);
            if(elLine)elSVG.removeChild(elLine);
        }
        set(res){
            let id = res.id;
            let el = document.getElementById(id);
            if(!el)return;
            el.setAttribute('x1',res.x1);
            el.setAttribute('y1',res.y1);
            el.setAttribute('x2',res.x2);
            el.setAttribute('y2',res.y2);
        }
        evt(res){
            let type = res.type;
        }

        clear(){
            let elSVG = document.getElementById('svg_contact');
            if(elSVG)elSVG.innerHTML = "";
        }

        show(){
            this.clear();
            let lines = __Arr__.contactLines;
            setTimeout(()=>{
                lines.forEach(el=>{
                    let id_s = el.id_s;
                    let id_e = el.id_e;
                    let s = __DB__.nodes[id_s];
                    let e = __DB__.nodes[id_e];

                    if(!s || !s.show)return;
                    if(!e || !e.show)return;

                    let s_mid = s.init.point.mid;
                    let e_mid = e.init.point.mid;
                    let x1 = s_mid[0];
                    let y1 = s_mid[1];
                    let x2 = e_mid[0];
                    let y2 = e_mid[1];

                    let newLine = {
                        id:'line_'+(new Date()).getTime(),
                        x1,y1,x2,y2
                    };
                    this.add_contact(newLine);

                    let lineEl = document.getElementById(newLine.id);
                    lineEl.oncontextmenu = (e)=>{
                        e.preventDefault();
                        e.stopPropagation();
                        __cntMenu__.use({
                            id:newLine.id,
                            type:'contactLine',
                            x:e.clientX,
                            y:e.clientY,
                        });
                    }
                });
            })
            



        }
    }

    // 节点模块
    class xNode {
        constructor(){}
        add(res){
            __format__.nodeInit({
                label   : res.label,
                id      : res.id, 
                pid     : res.pid,
                left    : res.left,
                html    : res.html||null,
            });

            __canvas__.load();
        }
        sel(){}
        del(res){
            let id = res.id;
            let node = __DB__.nodes[id];
            for(let i in __Arr__.nodes){
                let el = __Arr__.nodes[i];
                if(el.id === id){
                    __Arr__.nodes.splice(i,1);
                    delete __DB__.nodes[id];
                    break;
                }
            }
            
            // 删除关联节点-只删除数据，DOM元素在下次渲染时再删除（减少非必要的重排）
            let newArr = __Arr__.nodes.filter((el,i)=>{
                let sign = el.init.has;
                if(!sign && el.nodeType !== "free")delete __DB__.nodes[el.id];
                return sign;
            });

            __canvas__.load({items:__Arr__.nodes});

            __Arr__.nodes = newArr;
            __Arr__.nodes.forEach((el,i)=>{
                el.index = i;
                __DB__.nodes[el.id].index = i;
            });

            
        }
        update(){}
        reset(){

        }

        // 事件管理
        evt(){
            const nodes = document.querySelectorAll(".xmd-node")
            for(let el of nodes){
                let id = el.getAttribute('id');
                let label = el.childNodes[0].innerHTML;
                el.oncontextmenu = (e)=>{
                    e.stopPropagation();
                    e.preventDefault();
                    __cntMenu__.use({
                        x:e.clientX,
                        y:e.clientY,
                        id,
                    });
                }

                el.onmousedown = (e)=>{
                    e.stopPropagation();
                    e.preventDefault();

                    let cntEl = document.getElementById('cntMenu');
                    if(cntEl)return;
                    __draw__.nodeMouseDown({el,e});// 可拖拽节点点击事件
                }

                el.onmousemove = (e)=>{
                    if($nodeEvt.contactLine){
                        __cntMenu__.mousemove({el,e});
                    }
                }

                el.onmouseup = (e)=>{
                    // e.stopPropagation();
                    // e.preventDefault();
                    
                    for(let eel of nodes){
                        eel.classList.remove("xmd-active");
                    }
                    el.classList.add("xmd-active");

                    if($nodeEvt.contactLine){
                        __cntMenu__.mouseup({el,e});
                    }
                }

                el.childNodes[0].ondblclick = (e)=>{
                    if(!$init.editable)return;
                    let input = document.createElement('input');
                    input.setAttribute('id',"xmdEdit");
                    input.setAttribute('class',"xmd-edit");
                    el.appendChild(input);
                    input.focus();
                    input.value = el.childNodes[0].innerHTML;
                    input.onblur = (ee)=>{
                        let input = document.getElementById('xmdEdit');
                        if(input)el.removeChild(input);
                        let id = el.getAttribute('id');
                        let the = __DB__.nodes[id];

                        let txt = input.value;
                        the.label = txt;
                        __Arr__.nodes[the.index].label = txt;
                        el.childNodes[0].innerHTML = txt;

                        let w = el.getBoundingClientRect().width;
                        let h = el.getBoundingClientRect().height;
                        __Arr__.nodes[the.index].init.size.w = w;
                        __Arr__.nodes[the.index].init.size.h = h;
                        the.init.size.w = w;
                        the.init.size.h = h;

                        __canvas__.load();
                        
                    }
                }
            }
        }
    }

    // 数据解析模块
    class xFormate {
        constructor(){this.num=0}
        // 一维变多维
        otm(){
            let nodes = __Arr__.nodes;

            //解析过滤出自由节点
            __Arr__.frees = [];
            nodes.forEach((el,i)=>{
                __DB__.nodes[el.id].nodeType = __DB__.nodes[el.id].nodeType==='free'?'tree':__DB__.nodes[el.id].nodeType;
                if(el.pid !== null || el.root)return;
                el.nodeType = "free";
                el.pid = null,
                __DB__.nodes[el.id].nodeType = 'free';
                let itemEl = __tool__.deepCopy(el);

                __Arr__.frees.push(itemEl);

                $nEls[el.index].style.top  = el.init.poi.y + "px";
                $nEls[el.index].style.left = el.init.poi.x + "px";
            })

            $nodesMany = [];
            let nodesMany = nodes.map((el,i)=>{
                let childrenLeft = nodes.filter(item=>{
                    let sign = item.pid === el.id && item.left && !el.init.pull.l.plus;
                    if(sign)item.init.has = sign;
                    return sign;
                });

                let childrenRight = nodes.filter(item=>{
                    let sign = item.pid === el.id && !item.left && !el.init.pull.r.plus;
                    if(sign)item.init.has = sign;
                    return sign;
                });

                let children = nodes.filter(item=>{
                    if(item.pid === el.id){
                        if(el.id !== "root"){
                            if(item.id !== "root")item.left = el.left;
                        }
                    }
                    let plus = el.left?!el.init.pull.l.plus:!el.init.pull.r.plus;
                    
                    return item.pid === el.id && plus;
                });

                

                if(el.root){
                    el.init.has = true;
                    el.childrenRoot = {
                        left :childrenLeft,
                        right:childrenRight,
                    }
                }
                else{
                    if(el.nodeType === "node"){el.show = false;}
                    el.children = children;
                }

                return el;
            });

            $nodesMany = nodesMany.filter(item=>{
                return item.root;
            });

        }

        // 节点数据初始化
        nodeInit(el,i){
            if(el.init){
                if(el.left){
                    if(!el.init.pull.l.has){
                        el.init.pull.l.open = true;
                        el.init.pull.l.plus = false;
                    }
                }
                else{
                    if(!el.init.pull.r.has){
                        el.init.pull.r.open = true;
                        el.init.pull.r.plus = false;
                    }
                }
            }

            let obj = {
                id                  :el.id,
                pid                 :el.pid,
                root                :el.root||false,
                label               :el.label,
                type                :el.type||"text",
                nodeType            :el.nodeType||"node",
                data                :el.data||null,
                left                :el.left||false,
                html                :el.html||null,
                has                 :true,//判断当前节点是否还存在，如不存在会在下一次渲染时自动删除
                show                :true,//是否显示
                clsType             :el.clsType||null,
                index               :i,
                indexMany           :[],
                children            :[],
                childrenRoot        :{
                    left    :[],
                    right   :[],
                },
                init                :{
                    show    :false,
                    poi     :el.init?el.init.poi:{x:0,y:0},
                    point   :{a:[],b:[],c:[],d:[],ab:[],bc:[],cd:[],ad:[],mid:[]},
                    size    :{w:0,h:0},
                    max     :{top:0,h:0,w:0,ch:0},//h是包括节点自己的最大高，ch是除了自己所有子节点的最大高
                    maxRoot :{
                        l:{top:0,h:0,w:0,ch:0},//h是包括节点自己的最大高，ch是除了自己所有子节点的最大高
                        r:{top:0,h:0,w:0,ch:0},//h是包括节点自己的最大高，ch是除了自己所有子节点的最大高
                    },
                    pull    :{
                        l:{
                            show:false,
                            plus:false,
                        },
                        r:{
                            show:false,
                            plus:false,
                        }
                    },
                }
            };

            if(el.root){
                delete obj.children;
                delete obj.init.max;
            }
            else{
                delete obj.childrenRoot;
                delete obj.init.maxRoot;
            }

            __Arr__.nodes.push(obj);
            __DB__.nodes[el.id] = obj;
            $e.appendChild(__canvas__.node(el));
        }

        // 对外可被调用
        node(){
            this.otm();// 一维变多维
            this.node_maxw($nodesMany);
            this.node_show();
            this.node_maxh($nodesMany);            
            this.node_poi($nodesMany);
            this.node_pull($nodesMany);

            __Arr__.nodes.forEach(el=>{
                let id  = el.id;
                let itp = __tool__.idTrunPoi(id);
                __DB__.nodes[id].init.point = itp.point;
                el.init.point = itp.point;
            })

            this.line();
            __draw__.use();
        }

        // 解析节点 计算出节点的最大高
        node_maxh(items){
            recursion(items,null);
            return items;
            function recursion(items,parent){
                let max = {
                    l:{w:0,h:0},
                    r:{w:0,h:0},
                }
                items.forEach((el,i)=>{
                    if(el.root){
                        let rb = null,h = 0,w = 0,maxh = 0,maxw = 0;
                        if(el.childrenRoot.left.length > 0){
                            rb = recursion(el.childrenRoot.left,el);
                            // 最大高
                            maxh = rb.l.h;
                            el.init.maxRoot.l.ch = maxh;
                            h = el.init.size.h;
                            maxh = h<maxh?maxh:h;
                            el.init.maxRoot.l.h = maxh;

                            let mid = el.init.point.mid;
                            el.init.maxRoot.l.top = mid[1] - maxh/2;

                            __Arr__.nodes[0].init.maxRoot.l.top = el.init.maxRoot.l.top;
                            __Arr__.nodes[0].init.maxRoot.l.h   = maxh;
                        }

                        if(el.childrenRoot.right.length > 0){
                            let rb = recursion(el.childrenRoot.right,el);
                            // 最大高
                            maxh = rb.r.h;
                            el.init.maxRoot.r.ch = maxh;
                            h = el.init.size.h;
                            maxh = h<maxh?maxh:h;
                            el.init.maxRoot.r.h = maxh;

                            let mid = el.init.point.mid;
                            el.init.maxRoot.r.top = mid[1] - maxh/2;

                            __Arr__.nodes[0].init.maxRoot.r.top = el.init.maxRoot.r.top;
                            __Arr__.nodes[0].init.maxRoot.r.h = maxh;
                        }

                        return;
                    }

                    el.show = true;
                    if(parent && !parent.root)el.left = parent.left;

                    if(el.left){
                        fn_left(el);
                    }
                    else{
                        fn_right(el);
                    }
                })

                return max;
                
                function fn_left(item) {
                    let h = parseFloat(item.init.size.h.toFixed(2)) + $init.style.node.space_y;
                    // 如果没有同胞就减掉垂直间距
                    if(parent.root&&parent.childrenRoot.left.length === 1){
                        h = h - $init.style.node.space_y;
                    }
                    else if(parent.children && parent.children.length === 1){
                        h = h - $init.style.node.space_y;
                    }

                    if(item.children.length > 0){
                        let w1 = item.children[0].init.size.w + $init.style.node.space_x;
                        item.children.forEach(el=>{
                            let w = el.init.size.w + $init.style.node.space_x;
                            w1 = w1>w?w1:w;
                        });

                        max.l.w += w1;

                        let rb = recursion(item.children,item);
                        let maxh = rb.l.h;
                        item.init.max.ch = maxh;
                        maxh = h<maxh?maxh:h;
                        item.init.max.h = maxh;
                        max.l.h += maxh;
                        max.l.w += rb.l.w;
                        return;
                    }

                    item.init.max.h = h;
                    item.init.max.ch = h;
                    max.l.h += h;
                };
    
                function fn_right(item) {
                    let h = parseFloat(item.init.size.h.toFixed(2)) + $init.style.node.space_y;
                    // 如果没有同胞就减掉垂直间距
                    if(parent.root&&parent.childrenRoot.right.length === 1){
                        h = h - $init.style.node.space_y;
                    }
                    else if(parent.children && parent.children.length === 1){
                        h = h - $init.style.node.space_y;
                    }

                    if(item.children.length > 0){
                        let w1 = item.children[0].init.size.w + $init.style.node.space_x;
                        item.children.forEach(el=>{
                            let w = el.init.size.w + $init.style.node.space_x;
                            w1 = w1>w?w1:w;
                        });
                        max.r.w += w1;

                        let rb = recursion(item.children,item);
                        let maxh = rb.r.h;
                        item.init.max.ch = maxh;

                        maxh = h<maxh?maxh:h;
                        item.init.max.h = maxh;
                        max.r.h += maxh;
                        return;
                    }

                    
                    
                    item.init.max.h = h;
                    item.init.max.ch = h;
                    max.r.h += h;
                };
            }
        }

        // 解析节点 计算出节点的最大宽
        node_maxw(items){
            recursion(items);
            function recursion(items) {
                let maxw = 0;
                items.forEach(el=>{
                    if(el.root){
                        if(el.childrenRoot.left.length > 0){
                            recursion(el.childrenRoot.left);

                            let w1 = el.childrenRoot.left[0].init.max.w;
                            el.childrenRoot.left.forEach(item=>{
                                let w = item.init.max.w;
                                w1 = w1>w?w1:w;
                            });

                            el.init.maxRoot.l.w = w1;
                            __Arr__.nodes[0].init.maxRoot.l.w = w1;
                        }
                        if(el.childrenRoot.right.length > 0){
                            recursion(el.childrenRoot.right);

                            let w1 = el.childrenRoot.right[0].init.max.w;
                            el.childrenRoot.right.forEach(item=>{
                                let w = item.init.max.w;
                                w1 = w1>w?w1:w;
                            });

                            el.init.maxRoot.r.w = w1;
                            __Arr__.nodes[0].init.maxRoot.r.w = w1;
                        }
                    }
                    else{
                        el.show = true;
                        fn_child(el);
                    }
                })

                return maxw;

                function fn_child(item) {
                    let w = item.init.size.w + $init.style.node.space_x;
                    w = w/$scale;
                    item.init.max.w = w;
                    if(item.children.length > 0){
                        let rb = recursion(item.children);
                        let w1 = item.children[0].init.max.w;
                        item.children.forEach(el=>{
                            let w = el.init.max.w;
                            w1 = w1>w?w1:w;
                        });
                        maxw = w1 + w;
                        item.init.max.w = rb + w;
                    }
                    else{
                        maxw = maxw>w?maxw:w;
                    }
                }
            }
        }

        // 解析节点 计算出需要的坐标
        node_poi(items){
            $nEls = document.querySelectorAll('.xmd-node');
            recursion(items);
            return items;
            function recursion(items,parent){
                let myChild = [];
                items.forEach((el,i)=>{
                    if(el.root){
                        if(el.childrenRoot.left.length > 0){
                            myChild.push({children:el.childrenRoot.left,parent:el,root:true});
                        }

                        if(el.childrenRoot.right.length > 0){
                            myChild.push({children:el.childrenRoot.right,parent:el,root:true});
                        }
                        return;
                    }

                    myChild.push({
                        children:el.children,
                        parent:el,
                        left:el.left,
                    });

                    if(el.left){
                        fn_left(el,i);
                    }
                    else{
                        fn_right(el,i);
                    }
                });

                if(myChild.length === 0)return;

                for(let el of myChild){
                    recursion(el.children,el.parent);
                }

                function fn_left(item,index) {
                    if(!parent)return;
                    let pMid = parent.init.point.mid;
                    let pCh = parent.root?parent.init.maxRoot.l.ch:parent.init.max.ch;
                    let ph = parent.root?parent.init.maxRoot.l.h:parent.init.max.h;
                    let left = pMid[0] - parent.init.size.w/2 - $init.style.node.space_x - item.init.size.w;
                    let maxTop = pMid[1] - ph/2;

                    if(index === 0){
                        let oo = item.init.max.h/2 - item.init.size.h/2;//如果自己小于后代的高度，减掉除自身外的高度除2来做为本节点的TOP
                        if(ph > pCh){maxTop = pMid[1] - pCh/2;}// 如果自己比后代的高度要高度，改变最大TOP
                        
                        let top  = maxTop + oo;

                        item.init.max.top   = maxTop;
                        item.init.poi.x     = left;
                        item.init.poi.y     = top;
                        $nEls[item.index].style.left = left + "px";
                        $nEls[item.index].style.top  = top  + "px";
                    }
                    else{
                        let top = items[index-1].init.max.top + items[index-1].init.max.h;
                        item.init.max.top = top;
                        top = top + (item.init.max.h/2 - item.init.size.h/2);
                        item.init.poi.x = left;
                        item.init.poi.y = top;
                        $nEls[item.index].style.left = left + "px";
                        $nEls[item.index].style.top  = top + "px";
                    }
                    
                    
                    let itemPoi = __tool__.idTrunPoi(item.id);
                    item.init.point = itemPoi.point;
                };
    
                function fn_right(item,index) {
                    if(!parent)return;
                    let pMid = parent.init.point.mid;
                    let pCh = parent.root?parent.init.maxRoot.r.ch:parent.init.max.ch;
                    let ph = parent.root?parent.init.maxRoot.r.h:parent.init.max.h;
                    let left = pMid[0] + parent.init.size.w/2 + $init.style.node.space_x;
                    let maxTop = pMid[1] - ph/2;

                    if(index === 0){
                        let oo = item.init.max.h/2 - item.init.size.h/2;//如果自己小于后代的高度，减掉除自身外的高度除2来做为本节点的TOP
                        if(ph > pCh){maxTop = pMid[1] - pCh/2;}// 如果自己比后代的高度要高度，改变最大TOP
                        
                        let top  = maxTop + oo;

                        item.init.max.top   = maxTop;
                        item.init.poi.x     = left;
                        item.init.poi.y     = top;
                        $nEls[item.index].style.left = left + "px";
                        $nEls[item.index].style.top  = top  + "px";
                    }
                    else{
                        let top = items[index-1].init.max.top + items[index-1].init.max.h;
                        item.init.max.top = top;
                        top = top + (item.init.max.h/2 - item.init.size.h/2);

                        item.init.poi.x = left;
                        item.init.poi.y = top;
                        $nEls[item.index].style.left = left + "px";
                        $nEls[item.index].style.top  = top + "px";
                    }
                    
                    
                    let itemPoi = __tool__.idTrunPoi(item.id);
                    item.init.point = itemPoi.point;
                };
            }
        }

        // 子节点展开收起的图标
        node_pull(items){
            $nEls = document.querySelectorAll('.xmd-node');
            recursion(items);
            return items;
            function recursion(items,parent){
                items.forEach((el,i)=>{
                    let scale_border = el.init.size.h/2 - 7;
                    if(el.root){
                        if(el.childrenRoot.left.length > 0){
                            recursion(el.childrenRoot.left);
                            el.init.pull.l.show = true;
                            for(let ee of $nEls[el.index].childNodes){
                                if(ee.classList[0] === 'xmd-node-pull-l'){
                                    ee.classList.add("xmd-node-pull-remove");
                                    ee.style.top = scale_border + "px";
                                    break;
                                }
                            }
                        }

                        if(el.childrenRoot.right.length > 0){
                            recursion(el.childrenRoot.right);
                            el.init.pull.r.show = true;
                            for(let ee of $nEls[el.index].childNodes){
                                if(ee.classList[0] === 'xmd-node-pull-r'){
                                    ee.classList.add("xmd-node-pull-remove");
                                    ee.style.top = scale_border + "px";
                                    break;
                                }
                            }
                        }
                        return;
                    }

                    if(el.children.length > 0){
                        recursion(el.children);
                    }
                    else{
                        return;
                    }

                    if(el.left){
                        // if(!el.init.pull.l.has)return;
                        el.init.pull.l.show = true;
                        for(let ee of $nEls[el.index].childNodes){
                            if(ee.classList[0] === 'xmd-node-pull-l'){
                                ee.classList.add("xmd-node-pull-remove");
                                ee.style.top = scale_border + "px";
                                break;
                            }
                        }
                    }
                    else{
                        // if(!el.init.pull.r.has)return;
                        el.init.pull.r.show = true;
                        for(let ee of $nEls[el.index].childNodes){
                            if(ee.classList[0] === 'xmd-node-pull-r'){
                                ee.classList.add("xmd-node-pull-remove");
                                ee.style.top = scale_border + "px";
                                break;
                            }
                        }
                    }
                });

            }
        }

        // 子节点展开收起项
        node_show(){
            $nEls.forEach(el=>{
                let id = el.getAttribute('id');
                let the = __DB__.nodes[id];
                if(the.root)return;
                let opacity = the.show?"1":"0";
                let zIndex  = the.show?10:-1;
                let pointerEvents = the.show?"auto":"none";
                el.style.opacity = opacity;
                el.style.zIndex = zIndex;
                el.style.pointerEvents = pointerEvents;
            });
        }

        // 可被调用
        line(){
            this.line_init();
            this.line_draw();
        }

        // 线数据初始化
        line_init(){
            let _paths = [];
            recursion($nodesMany);
            __DB__.lines = _paths;
            function recursion(items){
                items.forEach(el=>{
                    if(el.root){
                        if(el.childrenRoot.left.length > 0){
                            rpg({
                                id:el.id,
                                label:el.label,
                                left:true,
                                children:el.childrenRoot.left,
                            });
                        }
                        
                        if(el.childrenRoot.right.length > 0){
                            rpg({
                                id:el.id,
                                label:el.label,
                                left:false,
                                children:el.childrenRoot.right,
                            });
                        }
                    }
                    else{
                        if(el.children.length > 0){
                            rpg({
                                id:el.id,
                                label:el.label,
                                left:el.left,
                                children:el.children,
                            });
                        }
                    }
                })
            }

            function rpg(item) {
                let s = {
                    id:item.id,
                    label:item.label,
                }

                if(item.left){
                    s.poi = __DB__.nodes[item.id].init.point.ad;
                }
                else{
                    s.poi = __DB__.nodes[item.id].init.point.bc;
                }

                item.children.forEach(el=>{
                    let e = {
                        id:el.id,
                        label:el.label,
                    }

                    e.poi = el.left?__DB__.nodes[el.id].init.point.bc:__DB__.nodes[el.id].init.point.ad;
                    _paths.push({
                        left:item.left,
                        s:__tool__.deepCopy(s),
                        e:__tool__.deepCopy(e),
                    });                    
                })
                if(item.children.length > 0)recursion(item.children);
            }
        }

        // 解析线数据 返回SVG的数据
        line_path(res){
            let s = {},
                e = {};

            let step = 30
            
            let sPoi = res.s.poi;
            let ePoi = res.e.poi;
            
            let s_mid_y = sPoi[1].toFixed(2);
            let e_mid_y = ePoi[1].toFixed(2);
            
            return res.left?fn_left():fn_right();
    
            function fn_left(){
                s.x = sPoi[0];
                s.y = sPoi[1];
                e.x = ePoi[0];
                e.y = ePoi[1];
    
                let M  = s.x  + "," + s.y;
                let L1 = (s.x - step) + "," + s.y;
                let L2 = (s.x - step) + "," + (e.y);
                let L3 = e.x + "," + e.y;
                let N1 = `a 10,10 0 0 0 -10,-10`;
                let N2 = `a 10,10 0 0 1 -10,10`;
                let A = '';
    
                if(s_mid_y > e_mid_y){
                    L2 = (s.x - step) + "," + (e.y + 10);
                    A = N1;
                }
                else if(s_mid_y < e_mid_y){
                    L2 = (s.x - step) + "," + (e.y - 10);
                    A = N2;
                }
    
                return `M ${M} L ${L1} L ${L2} ${A} L ${L3}`;
            }
    
            function fn_right(){
                s.x = sPoi[0];
                s.y = sPoi[1];
                e.x = ePoi[0];
                e.y = ePoi[1];
    
                let M  = (s.x - 0)  + "," + s.y;
                let L1 = (s.x + step) + "," + s.y;
                let L2 = (s.x + step) + "," + (e.y);
                let L3 = e.x + "," + e.y;
                // let N1  = `a 10,10 0 0 1 10,-10`;
                // let N2  = `a 10,10 0 0 0 10,10`;
                let N1  = `a 10,10 0 0 1 10,-10`;
                let N2  = `a 10,10 0 0 0 10,10`;
                let A = '';
    
                if(s_mid_y > e_mid_y){
                    L2 = (s.x + step) + "," + (e.y + 10);
                    A = N1;
                }
                else if(s_mid_y < e_mid_y){
                    L2 = (s.x + step) + "," + (e.y - 10);
                    A = N2;
                }
    
                return `M ${M} L ${L1} L ${L2} ${A} L ${L3}`;
            }
            
        }

        // 绘制线
        line_draw(){
            $e = document.getElementById('canvas');
            let oldSvg = document.getElementById('svg');
            if(oldSvg)$e.removeChild(oldSvg);
            let w = $e.getBoundingClientRect().width;
            let h = $e.getBoundingClientRect().height;

            let colors = $init.style.line.colors;// 颜色配置
            
            let len = colors.length;
            let lineSize = $init.style.line.size;
            let html = '';
            __DB__.lines.forEach(el=>{

                // 临时写的
                let color = colors[this.num];
                if(len > 1){
                    if(this.num === len - 1){
                        this.num = 0;
                    }
                    else{
                        this.num++;
                    }
                }

                let line = this.line_path(el);
                html += `
                    <path
                        fill="none"
                        stroke="${color}"
                        opacity="1"
                        stroke-width="${lineSize}"
                        pointer-events="stroke"
                        d="${line}"
                        ></path>
                `;
            });
            let htmlSvg = `
                <svg
                    class="xmd-svg"
                    id="svg"
                    width="${w}"
                    height="${h}"
                    version="1.1"
                    >${html}</svg>
                <svg
                    class="xmd-svg_contact"
                    id="svg_contact"
                    width="${w}"
                    height="${h}"
                    version="1.1"
                    >
                    </svg>
            `;
            $e.innerHTML = $e.innerHTML + htmlSvg;
        }

        // 清除画布上的信息
        clear(type="node"){
            if(type === "node"){
                __Arr__.nodes = [];
                __DB__.nodes = {};

                return;
            }
            
            if(type === "line"){
                __Arr__.lines = [];
                __DB__.lines = {};

                return;
            }
        }
    }

    // 公共工具类模块
    class xTool {
        constructor(){
            
        }

        // 通过ID 获取宽高xy 和坐标点
        idTrunPoi(id){ 
            let obj = {
                w:0,
                h:0,
                x:0,
                y:0,
                point:{}
            };
    
            let el = document.getElementById(id);
            let rect = el.getBoundingClientRect();
            let w = rect.width;
            let h = rect.height;
            w = w/$scale;
            h = h/$scale;
            w = parseInt(w * 1000000)/1000000;
            h = parseInt(h * 1000000)/1000000;

            let x = parseFloat(el.style.left);
            let y = parseFloat(el.style.top);
    
            let a  = [x , y];
            let b  = [x + w , y];
            let c  = [x + w , y + h];
            let d  = [x , y + h];
            let ab = [x + w/2 , y];
            let ad = [x , y + h/2];
            let bc = [x + w , y + h/2];
            let cd = [x , y + h];
            let mid = [x + w/2 , y + h/2];
    
            obj.w = w;
            obj.h = h;
            obj.x = x;
            obj.y = y;
            obj.point = {a,b,c,d,ab,bc,cd,ad,mid};
            
            return obj;
        }

        //数组对象深拷贝
        deepCopy(source){
            var sourceCopy = source instanceof Array ? [] : {};
            for (var item in source) {
                sourceCopy[item] = typeof source[item] === 'object' ? this.deepCopy(source[item]) : source[item];
            }
            return sourceCopy;
        }

        // 深度合并对象
        extends(a, b) { 
            for (var k in b) {
                if(a.hasOwnProperty(k)){
                    if(typeof b[k] === "object"){
                        if(b[k] instanceof Array){
                            a[k] = b[k];
                        }
                        else if(b[k] instanceof Function){
                            a[k] = b[k];
                        }
                        else if(Object.keys(b[k] > 0)){
                            this.extends(a[k],b[k]);
                        }
                        
                    }
                    else{
                        a[k] = b[k];
                    }
                    
                }

            }
            return a;
        }

        extends2(a, b) { 
            for (var key in b) {
                a[key] = a[key] && a[key].toString() === "[object Object]" ?
                    this.extends(a[key], b[key]) : a[key] = b[key];
            }
            return a;
        }

        // 防抖
        antiShake(fn,delay){
            let timer = null;
            return ()=>{
                timer&&clearTimeout(timer);
                timer=setTimeout(fn.bind(this, arguments),delay);
            }
        }

        // 节流
        throttle(fn,delay){
            let timer = null;
            return ()=>{
                if(timer!==null)return;
                timer=setTimeout(()=>{
                    timer = null;
                    return fn.apply(this, arguments);
                },delay);
            }
        }
    }

    // 拖拽模块
    class xDraw {
        constructor(){
            // 节点拖拽起始座标（屏幕）
            this.x = 0;
            this.y = 0;

            //鼠标拖拽后的距离差值
            this.diffXY = {
                x:0,y:0
            };

            this.drawId         = null;//拖拽节点的id
            this.drawPid        = null;//拖拽节点的pid
            this.drawType       = null;//拖拽的类型(in,up,down,free)
            this.drawTypeRoot   = null;//目标节点是否是根节点

            this.elDraw = null;

            //禁放区域
            this.va = {
                w:0,
                h:0,
                x:0,
                y:0,
            }
        }

        // 初始
        use(){
            // this.node();
            this.nodeUp();
            this.nodeDown();
            this.nodeIn();
            this.drawId = null;
            this.drawPid = null;
            this.drawType = null;
            this.drawTypeRoot = null;
        }

        // 拖拽对象
        drawObj(res){
            let id      = res.id;
            let label   = res.label;
            let type    = res.type;

            if(type === "add"){
                this.elDraw = null;
                this.elDraw = document.createElement('div');
                this.elDraw.setAttribute("id",'drawDiv');
                this.elDraw.setAttribute('class','xmd-drawDiv');
                this.elDraw.setAttribute('pid',id);
                this.elDraw.style.opacity = 0;
                this.elDraw.innerHTML = label;

                let w = document.getElementById(id).getBoundingClientRect().width;
                let h = document.getElementById(id).getBoundingClientRect().height;
                this.elDraw.style.minWidth = w + "px";
                this.elDraw.style.minHeight = h + "px";
                document.body.appendChild(this.elDraw);
                return;
            }

            if(type === "del"){
                document.body.removeChild(this.elDraw);
                this.elDraw = null;
                return;
            }
            
            let x = res.x;
            let y = res.y;
            this.elDraw.style.left      = x + "px";
            this.elDraw.style.top       = y + "px";
            this.elDraw.style.opacity   = 1;
        }

        // 禁止拖至子节点的区域
        nodeDisabledObj(res){
            let nodeDisabled = document.getElementById('nodeDisabled');
            if(nodeDisabled)$e.removeChild(nodeDisabled);
            if(res.type === 'del')return;

            let id = res.id;
            
            let nodeData = __DB__.nodes[id];
            nodeDisabled = document.createElement('div');
            nodeDisabled.setAttribute('id','nodeDisabled');
            nodeDisabled.setAttribute('class','nodeDisabled');
            let x = nodeData.init.poi.x,
                w = nodeData.init.size.w,
                maxw = nodeData.init.max.w,
                maxh = nodeData.init.max.h;
            let left = nodeData.left?x-maxw-40:x+w+40;
                
            nodeDisabled.style.width = maxw + "px";
            nodeDisabled.style.height = maxh + "px";
            nodeDisabled.style.background = $init.style.noSelectionArea.bgColor;
            nodeDisabled.style.position = "absolute";
            nodeDisabled.style.zIndex = "99";
            nodeDisabled.style.left = left + "px";
            nodeDisabled.style.top = nodeData.init.max.top + "px";
            $e.appendChild(nodeDisabled);
        }

        // 可拖拽节点点击事件
        nodeMouseDown(res){
            let e       = res.e,
                el      = res.el,
                id      = el.getAttribute('id'),
                label   = el.childNodes[0].innerHTML;
            if(__DB__.nodes[id].root || !__DB__.nodes[id])return;
            this.x = e.clientX;
            this.y = e.clientY;

            this.drawId = id;                 
            this.drawObj({type:"add",id,label});//生成拖拽对象
            this.visibleArea();// 拖拽区域-离开此区域会生成自由节点

            let ox = e.offsetX,
                oy = e.offsetY;
            document.onmousemove = (ev)=>{
                let x = ev.clientX - ox,
                    y = ev.clientY - oy;

                const nodesIn = document.querySelectorAll('.xmd-node-in');
                for(let el of nodesIn){
                    el.style.display = "block";
                }

                this.drawObj({x,y});// 设置拖拽对象位置
                this.nodeDisabledObj({id});//生成禁选区域（有子节点的节点使用）           
            }

            document.onmouseup = (ev)=>{
                document.onmousemove = null;
                document.onmouseup = null;

                this.drawObj({type:"del"});// 删除拖拽对象
                this.nodeDisabledObj({type:'del'});// 删除禁选区域（有子节点的节点使用）  
                this.visibleArea('hide');// 拖拽区域-离开会生成自由节点         

                this.diffXY.x = ev.clientX - this.x;
                this.diffXY.y = ev.clientY - this.y;

                this.up();// 拖拽结束
                this.clear(); // 清理拖拽
                

                const nodesIn = document.querySelectorAll('.xmd-node-in');
                for(let el of nodesIn){
                    el.style.display = "none";
                }
            }
        }
        
        // 进入节点
        nodeIn(){
            const nodesIn = document.querySelectorAll('.xmd-node-in');
            for(let el of nodesIn){
                el.addEventListener('mouseover',(e)=>{
                    e.stopPropagation();
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    let id = el.parentNode.getAttribute('id');
                    if(__DB__.nodes[id].nodeType === "free")return;
                    let drawPid = elDraw.getAttribute('pid');
                    if(drawPid === id)return;
                    
                    el.classList.add('active');

                    let x = parseFloat(el.parentNode.style.left),
                        y = parseFloat(el.parentNode.style.top),
                        w = parseFloat(el.parentNode.getBoundingClientRect().width),
                        h = parseFloat(el.parentNode.getBoundingClientRect().height);
                    
                    this.drawPid = id;
                    this.drawType = "in";
                    if(__DB__.nodes[id].root){
                        let theN = __DB__.nodes[id];
                        let w = theN.init.size.w;
                        el.onmousemove = (evv)=>{
                            let ox = evv.offsetX;
                            if(ox<=0 || ox >=w)return;
                            this.vBox({show:false});
                            __line__.del({id:"vline"});
                            if(ox > w/2){
                                this.drawType = "in";
                                this.drawTypeRoot = "right";
                                fn_right.call(this,{x,y,w,h});
                            }
                            else{
                                this.drawTypeRoot = "left";
                                fn_left.call(this,{x,y,w,h});
                            }
                        }
                        el.onmouseup = ()=>{
                            el.onmousemove = null;
                            el.onmouseup = null;
                        }
                        return;
                    }
                    
                    let left = __DB__.nodes[id].left;
                    if(left){
                        fn_left.call(this,{x,y,w,h});
                    }
                    else{
                        fn_right.call(this,{x,y,w,h});
                    }
                    
                });

                el.addEventListener('mouseout',(e)=>{
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    this.drawPid = null;
                    this.drawType = null;
                    this.clear();
                });
            }

            function fn_left(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;
                
                let x1 = x,
                    y1 = y + h/2,
                    x2 = x - 20,
                    y2 = y + h/2
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x - 90,
                    y:y + (h - 20)/2,
                    show:true
                });
            }

            function fn_right(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;
                
                let x1 = x + w,
                    y1 = y + h/2,
                    x2 = x + w + 20,
                    y2 = y + h/2
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x + w + 20,
                    y:y + (h - 20)/2,
                    show:true
                });
            }
        }

        // 进入节点上方
        nodeUp(){
            const nodesUp = document.querySelectorAll('.xmd-node-up');
            for(let el of nodesUp){
                el.addEventListener('mouseover',(e)=>{
                    e.stopPropagation();
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    let id = el.parentNode.getAttribute('id');
                    if(__DB__.nodes[id].nodeType === "free")return;
                    if(__DB__.nodes[id].root)return;
                    this.drawPid = id;
                    this.drawType = 'up';

                    let drawPid = elDraw.getAttribute('pid');
                    if(drawPid === id)return;
                    el.classList.add('active');

                    for(let item of el.parentNode.childNodes){
                        if(item.classList[0]==="xmd-node-in"){
                            item.classList.add('active');
                            break;
                        }
                    }

                    let x = parseFloat(el.parentNode.style.left),
                        y = parseFloat(el.parentNode.style.top),
                        w = parseFloat(el.parentNode.getBoundingClientRect().width),
                        h = parseFloat(el.parentNode.getBoundingClientRect().height);
                    
                    let left = __DB__.nodes[id].left;
                    if(left){
                        fn_left.call(this,{x,y,w,h});
                    }
                    else{
                        fn_right.call(this,{x,y,w,h});
                    }

                });

                el.addEventListener('mouseout',(e)=>{
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    this.drawPid = null;
                    this.drawType = null;
                    this.clear();
                });
            }

            function fn_left(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;
                
                let x1 = x,
                    y1 = y - 5,
                    x2 = x - 20,
                    y2 = y - 10
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x - 90,
                    y:y - 20,
                    show:true
                });
            }

            function fn_right(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;
                
                let x1 = x + w,
                    y1 = y - 5,
                    x2 = x + w + 20,
                    y2 = y - 10
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x + w + 20,
                    y:y - 20,
                    show:true
                });
            }
        }

        // 进入节点下方
        nodeDown(){
            const nodesDown = document.querySelectorAll('.xmd-node-down');
            for(let el of nodesDown){
                el.addEventListener('mouseover',(e)=>{
                    e.stopPropagation();
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    let id = el.parentNode.getAttribute('id');
                    if(__DB__.nodes[id].nodeType === "free")return;
                    if(__DB__.nodes[id].root)return;

                    let drawPid = elDraw.getAttribute('pid');
                    if(drawPid === id)return;
                    el.classList.add('active');
                    
                    this.drawPid = id;
                    this.drawType = "down";
                    for(let item of el.parentNode.childNodes){
                        if(item.classList[0]==="xmd-node-in"){
                            item.classList.add('active');
                            break;
                        }
                    }

                    let x = parseFloat(el.parentNode.style.left),
                        y = parseFloat(el.parentNode.style.top),
                        w = parseFloat(el.parentNode.getBoundingClientRect().width),
                        h = parseFloat(el.parentNode.getBoundingClientRect().height);

                    let left = __DB__.nodes[id].left;
                    if(left){
                        fn_left.call(this,{x,y,w,h});
                    }
                    else{
                        fn_right.call(this,{x,y,w,h});
                    }
                });

                el.addEventListener('mouseout',(e)=>{
                    let elDraw = document.getElementById('drawDiv');
                    if(!elDraw)return;
                    this.drawPid = null;
                    this.drawType = null;
                    this.clear();
                });
            }

            function fn_left(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;
                
                let x1 = x,
                    y1 = y + h + 5,
                    x2 = x - 20,
                    y2 = y + h +10 
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x - 90,
                    y:y + h,
                    show:true
                });
            }

            function fn_right(res) {
                let x = res.x,
                    y = res.y,
                    w = res.w/$scale,
                    h = res.h/$scale;

                let x1 = x + w,
                    y1 = y + h + 5,
                    x2 = x + w + 20,
                    y2 = y + h + 10
                
                __line__.add({
                    id:"vline",
                    x1,y1,x2,y2,
                });
                    
                this.vBox({
                    x:x + w + 20,
                    y:y + h,
                    show:true
                });
            }
        }

        // 节点的拖拽的上下区域是否显示 
        nodeDrawMain(res){
            let type = res.type;
            if(type === 'show'){
                let nodeUps = document.querySelectorAll('.xmd-node-up');
                for(let el of nodeUps){
                    el.style.display = "block";
                }

                let nodeDown = document.querySelectorAll('.xmd-node-down');
                for(let el of nodeDown){
                    el.style.display = "block";
                }
                return;
            }
            
            if(type === 'hide'){
                let nodeUps = document.querySelectorAll('.xmd-node-up');
                for(let el of nodeUps){
                    el.style.display = "none";
                }

                let nodeDown = document.querySelectorAll('.xmd-node-down');
                for(let el of nodeDown){
                    el.style.display = "none";
                }
                return;
            }
        }

        // 拖拽时的临时模块-预显示
        vBox(res){
            if(!res.show){
                let elBox = document.getElementById('vbox');
                if(elBox)$e.removeChild(elBox);
                return;
            }

            let x = res.x,
                y = res.y;
            let elBox = document.createElement('div');
            elBox.setAttribute('id','vbox');
            elBox.setAttribute('class','xmd-vbox');
            elBox.style.left = x + 'px';
            elBox.style.top  = y + 'px';
            $e.appendChild(elBox);
        }

        // 清理重置拖拽时的临时模块
        clear(){
            let nodeIns = document.querySelectorAll('.xmd-node-in');
            let nodeUps = document.querySelectorAll('.xmd-node-up');
            let nodeDowns = document.querySelectorAll('.xmd-node-down');
            nodeIns.forEach(el=>{
                el.setAttribute('class','xmd-node-in');
            });

            nodeUps.forEach(el=>{
                el.setAttribute('class','xmd-node-up');
            });

            nodeDowns.forEach(el=>{
                el.setAttribute('class','xmd-node-down');
            });

            this.vBox({show:false});// 清理拖拽时预显示块
            __line__.del({id:"vline"});// 清理拖拽时预显示线
        }

        // 拖拽节点
        up(){
            let id = this.drawId,
                pid = this.drawPid,
                type = this.drawType;
            
            if(id === pid)return;
            
            // 被拖拽的节点
            let theDiv = __tool__.deepCopy(__DB__.nodes[id]);
            let i_id = parseInt(theDiv.index);

            // 被拖拽节点的你节点
            let opid = __tool__.deepCopy(__DB__.nodes[pid]);
            let i_pid = parseInt(opid.index);

            let lleft = this.drawTypeRoot==="left"?true:false;

            // 一级节点不可拖拽的条件
            if(opid.root){
                if(theDiv.pid === pid && lleft === theDiv.left){
                    return;
                }
            }
            // 其他节点不可拖拽的条件
            else if(theDiv.pid === pid && type === "in" && theDiv.left === opid.left){return};

            // 旧的父节点
            let old_pid = theDiv.pid;
            let oo_old_pid = __DB__.nodes[old_pid];
            
            if(type === "in"){
                theDiv.pid = pid;
                theDiv.left = opid.left;
                
                if(__DB__.nodes[pid].root){
                    theDiv.left = this.drawTypeRoot === "left"?true:false;
                }
                __Arr__.nodes.splice(i_id,1);
                __Arr__.nodes.splice(__Arr__.nodes.length,0,theDiv);
                
                __DB__.nodes[id].pid = theDiv.pid;
                __DB__.nodes[id].left = theDiv.left;
            }
            else if(type === "up"){
                __Arr__.nodes.splice(i_id,1);
                theDiv.pid = opid.pid;
                theDiv.left = opid.left;
                let i = i_pid > i_id?i_pid-1:i_pid;
                __Arr__.nodes.splice(i,0,theDiv);
            }
            else if(type === "down"){
                __Arr__.nodes.splice(i_id,1);
                theDiv.pid = opid.pid;
                theDiv.left = opid.left;
                let i = i_pid < i_id?i_pid+1:i_pid;
                __Arr__.nodes.splice(i,0,theDiv);
            }

            // 自由节点
            else{
                let x_poi = __Arr__.nodes[i_id].init.poi.x;
                let y_poi = __Arr__.nodes[i_id].init.poi.y;
                let x_diff = this.diffXY.x;
                let y_diff = this.diffXY.y;
                
                let x = x_poi + x_diff,
                    y = y_poi + y_diff;

                let va = this.va;// 禁放区域
                // 如果拖拽后在中心区域内就取消拖拽
                if(va.w === 0 && va.h === 0)return;
                if((x > va.x && x < va.x + va.w) && (y > va.y && y < va.y + va.h))return;

                __Arr__.nodes[i_id].init.poi.x = x;
                __Arr__.nodes[i_id].init.poi.y = y;
                __Arr__.nodes[i_id].pid = null;

                __DB__.nodes[id].init.poi.x = x;
                __DB__.nodes[id].init.poi.y = y;
                __DB__.nodes[id].pid = null;
            }

            // 判断旧的父节点还有没有子节点
            let has = false;
            for(let eee of __Arr__.nodes){
                if(eee.pid === old_pid){
                    has = true;
                    break;
                }
            }

            if(oo_old_pid){
                if(oo_old_pid.left){
                    oo_old_pid.init.pull.l.has = has;
                    oo_old_pid.init.pull.l.has = has;
    
                    __DB__.nodes[old_pid].init.pull.l.has = has;
                    __Arr__.nodes[oo_old_pid.index].init.pull.l.has = has;
                }
                else{
                    oo_old_pid.init.pull.r.has = has;
                    oo_old_pid.init.pull.r.has = has;
    
                    __DB__.nodes[old_pid].init.pull.r.has = has;
                    __Arr__.nodes[oo_old_pid.index].init.pull.r.has = has;
                }
            }
            


            __Arr__.nodes.forEach((el,i)=>{
                __DB__.nodes[el.id].index = i;
                el.index = i;
            });

            

            //渲染节点
            __canvas__.load();
        }

        // 禁放区域
        visibleArea(type){
            if(type == 'hide'){
                let vaEl = document.getElementById('vaEl');
                if(vaEl){
                    $e.removeChild(vaEl);
                }
                return;
            }

            let rootItem = null;
            for(let el of __Arr__.nodes){
                if(el.root){
                    rootItem = el;
                    break;
                }
            }
            if(rootItem === null)return;

            let max = rootItem.init.maxRoot;
            let poi = rootItem.init.point.mid;

            let w = max.l.w + max.r.w + rootItem.init.size.w + $init.style.noSelectionArea.borderWidth;
            let h = max.l.h > max.r.h ? max.l.h : max.r.h;
            let x = poi[0] - max.l.w - $init.style.noSelectionArea.borderWidth;
            let y = poi[1] - h/2;

            let vaEl = document.getElementById('vaEl');
            if(vaEl){$e.removeChild(vaEl)}

            vaEl = document.createElement('div');
            vaEl.setAttribute('id','vaEl');
            vaEl.style.width    = (w + 0) + "px";
            vaEl.style.height   = (h + 100) + "px";
            vaEl.style.top      = (y - 50) + "px";
            vaEl.style.left     = x + "px";

            vaEl.style.backgroundColor = $init.style.noSelectionArea.bgColor;
            vaEl.style.position = 'absolute';
            vaEl.style.zIndex = '-2';

            this.va = {w,h,x,y};//禁放区域

            $e.appendChild(vaEl);
        }
    }

    // 右键
    class xContentMenu {
        constructor(){
            this.id     = null;     // 被操作的ID
            this.x      = 0;
            this.y      = 0;
            this.items  = [];       // 右键显示的项
            this.type   = "node";   // 右键显示的项类型
            this.lists  = {         // 右键的项集合-通过this.type来匹配
                node:[
                    {label:"添加节点",id:"add",name:"node"},
                    {label:"删除节点",id:"del",name:"node"},
                    {label:"关联连线",id:"line",name:"node"},
                ],
                contactLine:[
                    {label:"删除关联线",id:"del",name:"contactLine"}
                ]
            },
            this.line = {
                id:null,
                mid:null,
                data:{},
            };

            this.addItem = $init.contextMenu.addItem;
        }

        addItemfn(){
            this.addItem = $init.contextMenu.addItem;
            let addItem = this.addItem;
            if(addItem.length === 0)return;

            for(let el of addItem){
                this.lists.node.push({
                    label:el.label,
                    id:el.key,
                    name:"node",
                })
            }
        }

        use(res){
            this.init(res);// 初始化配置
            this.clear();// 创建右键菜单
            this.creat();
        }

        // 创建右键菜单
        creat(){
            let divClass = $init.style.contextMenu.class?" "+$init.style.contextMenu.class:"";
            let cntEl = document.createElement('div');
            cntEl.setAttribute('id','cntMenu');
            cntEl.setAttribute('class','xmd-contentMenu'+divClass);
            cntEl.style.cssText = `
                top             : ${this.y}px;
                left            : ${this.x}px;
            `;

            cntEl.onclick = (e)=>{
                e.stopPropagation();
                e.preventDefault();
            }

            let iEls = this.createItems();// 创建菜单项
            iEls.forEach(el=>{
                cntEl.appendChild(el);
            })

            document.body.append(cntEl);
            document.onclick = ()=>{
                this.clear();
                document.onclick = null;
            }
        }

        // 初始化配置
        init(res){
            this.id = res.id;
            this.x = res.x;
            this.y = res.y;
            let type = res.type||"node";
            this.type = type;
            
            this.items = this.lists[type];
            if(__DB__.nodes[res.id] && __DB__.nodes[res.id].root){
                this.items = this.items.filter(item=>{
                    return item.id !== "del";
                });
            }
        }

        // 创建菜单项-有返回值
        createItems(){
            let items = this.items;
            if(items.length === 0)return;
            let htmlEl = [];
            items.forEach(el=>{
                let iEl = document.createElement('div');
                iEl.setAttribute('class','xmd-cntItem');
                iEl.setAttribute('id',el.id);
                iEl.setAttribute('name',el.name);
                iEl.innerHTML = el.label;
                htmlEl.push(iEl);

                iEl.onclick = (e)=>{
                    let type = iEl.getAttribute('id');
                    let name = iEl.getAttribute('name');
                    this.evt({type,name});
                    this.clear();
                }
            });

            return htmlEl;
        }

        // 清除右键菜单
        clear(){
            let cntEl = document.getElementById('cntMenu');
            if(!cntEl)return;
            document.body.removeChild(cntEl);
        }

        // 事件处理
        evt(res){
            let type = res.type,
                name = res.name,
                id   = this.id;
            let node = __DB__.nodes[id];

            if(name === "node"){
                fn_node.call(this)
                return;
            }

            if(name === "contactLine"){
                fn_contactLine.call(this)
            }

            function fn_node(params) {
                if(type === "add"){
                    __node__.add({
                        label:"新节点",
                        id:(new Date()).getTime() + "_node",
                        pid:id,
                        left:node.left
                    });
                    return;
                }
    
                if(type === "del"){
                    __node__.del({id});
                    return;
                }
    
                if(type === 'line'){
                    this.contactLine({id});// 关联线
                    return;
                }

                for(let el of this.addItem){
                    if(el.key === type){
                        el.fn(__DB__.nodes[id]);
                    }
                };
            }
            
            function fn_contactLine(params) {
                if(type === "del"){
                    __line__.del_contact({id})
                    return;
                }
            }
        }

        // 关联线
        contactLine(res){
            let id = res.id;
            let node = __DB__.nodes[id];
            
            let mid = node.init.point.mid;
            let lineId = "line_"+(new Date()).getTime()+"_"+id;
            this.line.id = lineId;
            this.line.nodeId = id;
            this.line.mid = mid;
            $nEls = document.querySelectorAll('.xmd-node');
            $nodeEvt.contactLine = true;
            __draw__.nodeDrawMain({type:'hide'});// 节点的拖拽的上下区域是否显示

            document.onmousemove = (e)=>{
                let x = e.offsetX;
                let y = e.offsetY;

                __line__.del_contact({id:lineId});
                __line__.add_contact({
                    id:lineId,
                    x1:mid[0],
                    y1:mid[1],
                    x2:x,
                    y2:y,
                });
            }
            
            $e.onclick = (e)=>{
                document.onmousemove = null;
                document.onmouseup = null;
                if(!lineId)return;
                __line__.del_contact({id:lineId});
                this.line.id = null;
                this.line.mid = null;

                $nodeEvt.contactLine = false;
                __draw__.nodeDrawMain({type:'show'});// 节点的拖拽的上下区域是否显示
            }
        }

        mousemove(res){
            let el = res.el,
                e = res.e;
            e.preventDefault();
            e.stopPropagation();

            let line = {};
            let lineId = this.line.id;
            let mid = this.line.mid;
            let eId = el.getAttribute('id');
            let left = __DB__.nodes[eId].init.point.a[0];
            let top = __DB__.nodes[eId].init.point.a[1];
            
            line = {
                id:lineId,
                x1:mid[0],
                y1:mid[1],
                x2:left + e.offsetX - 0,
                y2:top + e.offsetY - 10,
                type:"contactLine",
            }

            this.line.data = line;

            __line__.set(line);            
        }

        mouseup(res){
            let el = res.el,
                e = res.e;

            let eId = el.getAttribute('id'); // 目标节点ID
            let id = this.line.nodeId;// 被操作的节点ID

            // 关联自己则返回
            if(eId === id)return;
            // 已关联则返回
            let sign = true;
            for(let ee of __Arr__.contactLines){
                if(ee.id_s === id && ee.id_e === eId || ee.id_s === eId && ee.id_e === id){
                    sign = false;
                    break;
                }
            }
            if(!sign)return;

            this.line.data.id_s = id;
            this.line.data.id_e = eId;
            __Arr__.contactLines.push(this.line.data);
            __DB__.contactLines[eId] = (this.line.data);
            __line__.show();            
        }
    }

    const __canvas__    = new xCanvas(),
          __opt__       = new xOpt(),
          __line__      = new xLine(),
          __node__      = new xNode(),
          __format__    = new xFormate(),
          __draw__      = new xDraw(),
          __cntMenu__   = new xContentMenu(),
          __tool__      = new xTool();
 })(window);
